package ac.github.oa.api.common.script

import org.bukkit.entity.LivingEntity
import taboolib.common.LifeCycle
import taboolib.common.io.getInstance
import taboolib.common.io.runningClasses
import taboolib.common.platform.Awake
import java.util.concurrent.ConcurrentHashMap

object StringScript {

    val scripts = mutableMapOf<String, InlineScript>()

    val cache = ConcurrentHashMap<String, List<Stream>>()

    @Awake(LifeCycle.ENABLE)
    fun loadScripts() {
        runningClasses.forEach {
            if (InlineScript::class.java.isAssignableFrom(it)) {
                val script = it.getInstance()?.get() as? InlineScript ?: return@forEach
                scripts[script.id] = script
            }
        }
    }

    fun parse(source: String, sender: LivingEntity): String {
        return parse(source, InlineScriptContext(sender))
    }

    fun parse(sources: List<String>, sender: LivingEntity): List<String> {
        return sources.map { parse(it, sender) }
    }

    fun parse(sources: List<String>, context: InlineScriptContext): List<String> {
        return sources.map { parse(it, context) }
    }

    fun parse(string: String, context: InlineScriptContext): String {
        var source = string
        val streams = of(source)
        streams.forEach {
            val invokeStream = invokeStream(it, context)

            source = source.replaceFirst("{${it.source}}", invokeStream)
            if (it.namespace != null) {
                context.data[it.namespace] = invokeStream
            }
        }
        return source
    }

    fun invokeStream(stream: Stream, context: InlineScriptContext): String {

        if (stream.namespace != null && context.data.containsKey(stream.namespace)) {

            return context.data[stream.namespace]!!.toString()
        }


        var temp = if (stream.namespace != null) {
            stream.source.removePrefix("$${stream.namespace}:${stream.scriptId}:")
        } else {
            stream.source.removePrefix("${stream.scriptId}:")
        }

        // invoke child
        stream.child.forEach {
            temp = temp.replaceFirst("{${it.source}}", invokeStream(it, context))
        }

        val script = scripts[stream.scriptId] ?: return temp
        return script.handleScript(temp, context) ?: "null"
    }

    fun of(source: String): List<Stream> {
        return cache.computeIfAbsent(source) {
            getElements(source).map { readTreat(it) }
        }
    }

    fun readTreat(singleSource: String): Stream {
        var source = singleSource
        var id: String? = null
        if (source[0] == '$') {
            id = source.substring(1, source.indexOf(':'))
            source = source.substring(id.length + 2) // $ :
        }

        val scriptId = if (source.indexOf(":") != -1) {
            val substring = source.substring(0, source.indexOf(":"))
            source = source.substring(substring.length + 1)
            substring
        } else source
        // check child node
        return Stream(id, scriptId, singleSource, getElements(source).map { readTreat(it) })
    }

    fun getElements(source: String): List<String> {
        var cycle = 0
        val elements = mutableListOf<String>()
        var markIndex = 0
        source.toCharArray().forEachIndexed { index, c ->

            when (c) {
                '{' -> {
                    if (cycle++ == 0) {
                        markIndex = index + 1
                    }
                }

                '}' -> {
                    if (--cycle == 0) {
                        elements += source.substring(markIndex, index)
                        markIndex = 0
                    }
                }
            }

        }
        return elements
    }


    class Stream(val namespace: String? = null, val scriptId: String, val source: String, val child: List<Stream>) {

        override fun toString(): String {
            return "Stream(namespace=$namespace, scriptId='$scriptId', source='$source', child=$child)"
        }
    }

}